package com.xiaomaoguai.fcp.pre.kepler.glue.autoconfiguration;

import com.xiaomaoguai.fcp.pre.kepler.glue.exception.GlueException;
import com.xiaomaoguai.fcp.pre.kepler.glue.handler.GlueHandler;
import com.xiaomaoguai.fcp.pre.kepler.glue.config.GlueConfig;
import com.xiaomaoguai.fcp.pre.kepler.glue.model.GlueClass;
import com.xiaomaoguai.fcp.pre.kepler.glue.service.GlueInitService;
import com.xiaomaoguai.fcp.pre.kepler.glue.utils.FieldUtils;
import com.xiaomaoguai.fcp.pre.kepler.glue.utils.SpringContextHolder;
import groovy.lang.GroovyClassLoader;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.annotation.AnnotationUtils;

import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author WeiHui-Z
 * @version v1.0.0
 * @date 2019/9/23 17:51
 * @since JDK 1.8
 */
@Slf4j
public class GlueBootstrap extends GlueConfig implements ApplicationContextAware {

	/**
	 * 逻辑单元缓存器
	 */
	private static final Map<String, GlueHandler> GLUE_HANDLER_CACHE = new ConcurrentHashMap<>(32);

	/**
	 * 懒加载，第一次使用再加载，因为groovy这种用的很少
	 */
	private static final Map<String, GlueClass> GLUE_CLASS_CACHE = new ConcurrentHashMap<>(32);

	/**
	 * groovy加载器
	 */
	private GroovyClassLoader groovyClassLoader = new GroovyClassLoader();

	private GlueInitService glueInitService;

	public GlueBootstrap(GlueInitService glueInitService) {
		this.glueInitService = glueInitService;
	}

	/**
	 * 加载或者获取新的执行单元
	 *
	 * @param glueSource 类源代码
	 * @return GlueHandler glue执行类
	 * @throws Exception ex
	 */
	private GlueHandler loadNewInstance(String glueSource) throws Exception {
		if (StringUtils.isNotBlank(glueSource)) {
			Class<?> clazz = groovyClassLoader.parseClass(glueSource);
			if (clazz != null) {
				String clazzName = clazz.getName();
				Object instance = clazz.newInstance();
				if (instance != null) {
					if (instance instanceof GlueHandler) {
						this.injectService(instance);
						log.info(">>>>>>>>>>>> xxl-glue, loadNewInstance success, name:{}", clazzName);
						return (GlueHandler) instance;
					}
				} else {
					throw new IllegalArgumentException(">>>>>>>>>>> xxl-glue, loadNewInstance error, "
							+ "cannot convert from instance[" + clazzName + "] to GlueHandler");
				}
			}
		}
		throw new IllegalArgumentException(">>>>>>>>>>> xxl-glue, loadNewInstance error, instance is null");
	}

	/**
	 * 注入spring资源
	 *
	 * @param instance 实例
	 */
	private void injectService(Object instance) throws IllegalAccessException {
		if (instance != null) {
			List<Field> fields = FieldUtils.getDeclaredFields(instance.getClass());
			for (Field field : fields) {
				if (FieldUtils.isStatic(field)) {
					continue;
				}
				Object fieldBean = null;
				Resource resource = AnnotationUtils.getAnnotation(field, Resource.class);
				Autowired autowired = AnnotationUtils.getAnnotation(field, Autowired.class);
				if (resource != null) {
					fieldBean = getResourceBean(field, resource);
				}
				if (autowired != null) {
					fieldBean = getAutowiredBean(field);
				}
				if (fieldBean != null) {
					field.setAccessible(true);
					field.set(instance, fieldBean);
				}
			}
		}
	}

	private Object getResourceBean(Field field, Resource resource) {
		Object fieldBean = null;
		try {
			String resourceName = resource.name();
			if (StringUtils.isNotBlank(resourceName)) {
				fieldBean = SpringContextHolder.getBean(resourceName);
			} else {
				fieldBean = SpringContextHolder.getBean(field.getName());
			}
		} catch (BeansException e) {
			log.debug("glue注入依赖找不到bean", e);
		}
		if (fieldBean == null) {
			fieldBean = SpringContextHolder.getBean(field.getType());
		}
		return fieldBean;
	}

	private Object getAutowiredBean(Field field) {
		Object fieldBean;
		Qualifier qualifier = AnnotationUtils.getAnnotation(field, Qualifier.class);
		if (qualifier != null && StringUtils.isNotBlank(qualifier.value())) {
			fieldBean = SpringContextHolder.getBean(qualifier.value());
		} else {
			fieldBean = SpringContextHolder.getBean(field.getType());
		}
		return fieldBean;
	}

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		SpringContextHolder.setApplicationContext((ConfigurableApplicationContext) applicationContext);
		this.glueInitService.initialization(this);
	}

	public void addOrUpdateGlueClass(String glueName, GlueClass glueClass) {
		GlueClass old = GLUE_CLASS_CACHE.put(glueName, glueClass);
		if (old != null) {
			GLUE_HANDLER_CACHE.remove(glueName);
		}
	}

	public void removeGlueClass(String glueName) {
		if (GLUE_CLASS_CACHE.remove(glueName) != null) {
			GLUE_HANDLER_CACHE.remove(glueName);
		}
	}

	public Map<String, GlueClass> getAllGlueClass() {
		return Collections.unmodifiableMap(GLUE_CLASS_CACHE);
	}

	public Object run(String glueName, Map<String, Object> params) {
		Optional<GlueHandler> glueHandler = Optional.ofNullable(GLUE_HANDLER_CACHE.computeIfAbsent(glueName, g -> {
			try {
				GlueClass glueClass = GLUE_CLASS_CACHE.get(g);
				if (glueClass != null) {
					return loadNewInstance(glueClass.getGlueSource());
				}
			} catch (Exception e) {
				log.error("加载Glue异常", e);
			}
			return null;
		}));
		return glueHandler.orElseThrow(() -> new GlueException("加载Glue异常")).handle(params);
	}

}
